package compilers.AST.basics;

import java.util.Iterator;
import java.util.LinkedList;

import compilers.AST.Node;
import compilers.AST.expression.Expression;
import compilers.AST.types.TypeSpecifier;
import compilers.compiler.PGenerator;
import compilers.exceptions.SemanticalException;
import compilers.symbolTable.SymbolTableArrayElement;
import compilers.symbolTable.SymbolTableScope;

public class ArrayValueList extends Node {

	private LinkedList<Expression> fExpressions;
	
	public ArrayValueList() {
		fExpressions = new LinkedList<Expression>();
	}
	
	public ArrayValueList(int lineNr) {
		super(lineNr);
		fExpressions = new LinkedList<Expression>();
	}
	
	public void print(int identLevel) {
		println(identLevel, "ArrayValueList");
		Iterator<Expression> it = fExpressions.iterator();
		while (it.hasNext()) {
			it.next().print(identLevel+1);
		}
	}
	
	public void checkSemantics(SymbolTableScope currentScope) throws SemanticalException {
		if (!fExpressions.isEmpty()) {
			Iterator<Expression> it = fExpressions.iterator();
			int generalType = 0;
			while (it.hasNext()) {
				Expression expr = it.next();
				expr.checkSemantics(currentScope);
				if (generalType == 0) {
					generalType = expr.getResultType(currentScope);
				}
				int type = expr.getResultType(currentScope);
				boolean correct = true;
				if (type != generalType) {
					if (generalType == TypeSpecifier.INTEGER) {
						if (type != TypeSpecifier.FLOAT) {
							correct = false;
						} else {
							generalType = TypeSpecifier.FLOAT;
						}
					} else if (generalType == TypeSpecifier.FLOAT) {
						if (type != TypeSpecifier.INTEGER) {
							correct = false;
						}
					} else {
						correct = false;
					}
				}
				if (!correct) {
					throw new SemanticalException("All the expressions in the array value list need to be of the same type.", fLineNr);
				}
			}
		}
	}
	
	public void addExpression(Expression expression) {
		fExpressions.add(expression);
	}
	
	public int getResultType(SymbolTableScope currentScope) {
		if (!fExpressions.isEmpty()) {
			Iterator<Expression> it = fExpressions.iterator();
			return it.next().getResultType(currentScope);
		}
		return 0;
	}
	
	// If init is true, then the array was already initalized with zero-values
	public void generatePcode(PGenerator pGen, SymbolTableScope currentScope, SymbolTableArrayElement array, boolean init) {
		
		// If the array was not already initalized
		if (!init) {
			
			// Assign values
			if (!fExpressions.isEmpty()) {
				Iterator<Expression> it = fExpressions.iterator();
				while (it.hasNext()) {
					Expression expr = it.next();
					if (expr.isConstant(currentScope)) {
						//If the value has to be cast to another type, we have to do this in advance
						String value = "";
						if(expr.getOriginalType(currentScope) != expr.getResultType(currentScope)) {
							value = expr.calculateValue(currentScope).toType(expr.getResultType(currentScope));
						} else if (array.getType().getType(currentScope) != expr.getResultType(currentScope)) {
							value = expr.calculateValue(currentScope).toType(array.getType().getType(currentScope));
						} else {
							value = expr.calculateValue(currentScope).valueToString();
						}
						pGen.load(array.getType().getPType(currentScope), value);
					} else {
						expr.generatePcode(pGen, currentScope);
						if (array.getType().getType(currentScope) != expr.getResultType(currentScope)) {
							pGen.conv('i', 'r');
						}
					}
				}
				
			// Assign zero-values when the value list is empty	
			} else {
				String label = "arrayInit_" + fNodeId;
				pGen.load('i', "0");
				pGen.storeAtAddress('i', 0);
				pGen.beginLabel(label);
				pGen.loadFromAddress('i', 0);
				pGen.lod('i', currentScope.getDepth(), array.getMemoryAddress() + 1);
				pGen.less('i');
				pGen.conditionalJump("end_" + label);
				if (array.getType().getType(currentScope) == TypeSpecifier.FLOAT) {
					pGen.load('r', "0.0");
				} else {
					pGen.load(array.getType().getPType(currentScope), "0");
				}
				pGen.loadFromAddress('i', 0);
				pGen.inc('i', 1);
				pGen.storeAtAddress('i', 0);
				pGen.unconditionalJump("begin_" + label);
				pGen.endLabel(label);
			}
			
		// Otherwise (when arraylistassignment is present)	
		} else {
		
			// Assign values
			if (!fExpressions.isEmpty()) {
				
				// Load relative start address on stack
				pGen.lod('a', currentScope.getDepth(), array.getMemoryAddress());
				pGen.conv('a', 'i');
				
				// Convert address to actual start address using nesting depths
				pGen.lda(currentScope.getDepth(), 0);
				pGen.conv('a', 'i');
				pGen.add('i');
				
				// Store actual start address in temp variable
				pGen.storeAtAddress('i', 0);
				
				// Assign values
				Iterator<Expression> it = fExpressions.iterator();
				int index = 0;
				while (it.hasNext()) {
					Expression expr = it.next();
					
					// Add index to actual index
					pGen.loadFromAddress('i', 0);
					pGen.load('i', Integer.toString(index));
					index++;
					pGen.add('i');
					pGen.conv('i', 'a');
					
					// Load rValue on stack
					if (expr.isConstant(currentScope)) {
						//If the value has to be cast to another type, we have to do this in advance
						String value = "";
						if(expr.getOriginalType(currentScope) != expr.getResultType(currentScope)) {
							value = expr.calculateValue(currentScope).toType(expr.getResultType(currentScope));
						} else if (array.getType().getType(currentScope) != expr.getResultType(currentScope)) {
							value = expr.calculateValue(currentScope).toType(array.getType().getType(currentScope));
						} else {
							value = expr.calculateValue(currentScope).valueToString();
						}
						pGen.load(array.getType().getPType(currentScope), value);
					} else {
						expr.generatePcode(pGen, currentScope);
						if (array.getType().getType(currentScope) != expr.getResultType(currentScope)) {
							pGen.conv('i', 'r');
						}
					}
					
					// Store
					pGen.sto(array.getType().getPType(currentScope));
				}
			}
		}
	}
}
